package com.starleague.battletech.rules;

import java.util.EnumSet;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.starleague.StarLeagueException;
import com.starleague.battletech.BattletechException;
import com.starleague.battletech.game.BattletechPhase;
import com.starleague.battletech.game.IBattletechTeam;
import com.starleague.battletech.game.IMutableBattletechGame;
import com.starleague.battletech.hexagonal.BattletechHexTerrainType;
import com.starleague.battletech.hexagonal.IBattletechHex;
import com.starleague.battletech.hexagonal.IBattletechHexTerrain;
import com.starleague.battletech.unit.IMutableBattletechUnit;

public abstract class AbstractPhaseState
{
    private static final Logger log = LoggerFactory.getLogger(AbstractPhaseState.class);

    private final BattletechPhase phase;
    protected final BattletechDice dice;
    private final AbstractPhaseState nextPhaseState;
    private final Set<BattletechPhase> previousPhaseSet;

    protected AbstractPhaseState(BattletechDice dice,
                                 BattletechPhase phase,
                                 AbstractPhaseState nextPhaseState,
                                 BattletechPhase previousPhase,
                                 BattletechPhase... previousPhases)
    {
        this.dice = dice;
        this.phase = phase;
        this.nextPhaseState = nextPhaseState;
        this.previousPhaseSet = EnumSet.of(previousPhase, previousPhases);
    }

    /**
     * @throws StarLeagueException
     */
    public void startPhase(IMutableBattletechGame game)
        throws StarLeagueException
    {
        Preconditions.checkState((game.getTeams().size() == 2),
                                 "Must have exactly 2 Teams, Found [%s]",
                                 game.getTeams().size());

        if (!this.previousPhaseSet.contains(game.getPhase()))
        {
            throw new BattletechException("Cannot transition from ["
                                          + game.getPhase()
                                          + "] to ["
                                          + this.phase
                                          + "]: "
                                          + game);
        }

        log.info("Starting Phase [{}]", this.phase);

        // phaseStarting(game);
        game.setPhase(this.phase);

        // log.info("Phase Started [{}]", game.getPhase());

        // phaseStarted(game);
    }

    // /**
    // * @throws StarLeagueException
    // */
    // public void phaseStarting(IMutableBattletechGame game) throws StarLeagueException
    // {
    // if (!this.previousPhaseSet.contains(game.getPhase()))
    // {
    // throw new BattletechException("Cannot transition from [" + game.getPhase() + "] to [" + this.phase + "]");
    // }
    // }
    //
    // /**
    // * @throws StarLeagueException
    // */
    // public void phaseStarted(IMutableBattletechGame game) throws StarLeagueException
    // {
    // game.setPhase(this.phase);
    // }

    protected void endPhase(IMutableBattletechGame game)
        throws StarLeagueException
    {
        log.info("Phase Ended [{}]", game.getPhase());

        // TODO: Determine if there is a winner

        this.nextPhaseState.startPhase(game);
    }

    protected void verifyPhase(IMutableBattletechGame game)
    {
        Preconditions.checkState((game.getPhase() == this.phase), "Game is not in Phase [%s]: %s", this.phase, game);
    }

    // public static final <E> List<E> cloneList(Collection<E> srcList)
    // {
    // return new ArrayList<E>(srcList);
    // }

    // protected IBattletechHex getHexAt(IBattletechGame game,
    // IHexCoordinate hexCoordinate) throws HexOutOfBoundsException
    // {
    // return game.getHexGrid().getHexAt(hexCoordinate);
    // }

    protected static int calcMechElevation(IBattletechHex hex)
    {
        int elevation = hex.getElevation();

        IBattletechHexTerrain hexTerrainWater = findHexTerrainOfType(hex, BattletechHexTerrainType.WATER);
        if (hexTerrainWater != null)
            elevation -= hexTerrainWater.getLevel();

        return elevation;
    }

    protected static final IBattletechHexTerrain findHexTerrainOfType(IBattletechHex hex,
                                                                      BattletechHexTerrainType hexTerrainType)
    {
        for (IBattletechHexTerrain hexTerrain : hex.getHexTerrainList())
            if (hexTerrain.getHexTerrainType() == hexTerrainType)
                return hexTerrain;
        return null;
    }

    protected static final Predicate<IMutableBattletechUnit> onTeamPredicate(final IBattletechTeam team)
    {
        return new Predicate<IMutableBattletechUnit>()
        {
            @Override
            public boolean apply(IMutableBattletechUnit unit)
            {
                return unit.getTeam() == team;
            }
        };
    }
}
